package com.nulldev.util.other.arrays;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.java.CloneUtils;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.threading.v3.RunnableWorker;
import com.nulldev.util.threading.v3.ThreadDispatcher;

/**
 * A queued map preventing Exceptions caused from reading maps too quickly. <br>
 * <b> WARNING: </b> Calling any function except close(), size(), addContext() will block the current thread!
 * 
 * @author null
 * @since nullUtil 2.6 - build 204
 * @param <K>
 * @param <V>
 */
public class QueuedMap<K, V> implements Map<K, V>, Cloneable, Closeable, Runnable {
	
	private final Map<K, V> list;
	private final LinkedList<QueueContext<Map<K, V>>> queue = 
			new LinkedList<QueueContext<Map<K, V>>>();
	private static final Logger log = LoggerFactory.getLogger(true, true);
	
	public QueuedMap(final Map<K, V> list) {
		this.list = list;
		ThreadDispatcher.instance().addTask(RunnableWorker.fromRunnable(this, "$QueuedMap.Run"));
	}
	
	private boolean __is_running, stop;
	
	public void start() {
		if (this.__is_running) {
			return;
		}
		ThreadDispatcher.instance().addTask(RunnableWorker.fromRunnable(this, "$QueuedMap.Run"));
	}
	
	@Override
	public void run() {
		if (this.__is_running) {
			return;
		}
		this.__is_running = true;
		while (!this.stop) {
			try {
				if (this.list == null) {
					log.error("Queue STOPPED: List is null!");
					break;
				}
				if (!this.queue.isEmpty()) {
					final QueueContext<Map<K, V>> context = this.queue.removeFirst();
					if (context == null) {
						//JVMUtil.async_println("NO CONTEXT FOUND!");
						try {
							Thread.sleep(5L);
						}catch (Exception e) {}
					}else {
						//JVMUtil.async_println("Context ready!");
						context.onContext(this.list);
					}
				}
			}catch (Exception e) {
				log.error("Failed a context task!", e);
			}
		}
		this.__is_running = false;
		this.stop = false;
	}

	@Override
	public void close() throws IOException {
		this.stop = true;
	}
	
	@Override
	public QueuedMap<K, V> clone() {
		final QueuedMap<K, V> clone = new QueuedMap<K, V>((Map<K, V>) CloneUtils.clone(this.list));
		return clone;
	}
	
	public boolean addContext(final QueueContext<Map<K, V>> context) {
		return this.queue.add(context);
	}

	@Override
	public int size() {
		return this.list.size();
	}

	@Override
	public boolean isEmpty() {
		return this.list.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		final FinalBypass<Boolean> result = FinalBypass.createBypass(Boolean.class);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.containsKey(key));
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public boolean containsValue(Object value) {
		final FinalBypass<Boolean> result = FinalBypass.createBypass(Boolean.class);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.containsValue(value));
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public V get(final Object key) {
		final FinalBypass<V> result = new FinalBypass<V>(null);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.get(key));
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public V put(K key, V value) {
		final FinalBypass<V> result = new FinalBypass<V>(value);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.put(key, value));
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public V remove(Object key) {
		final FinalBypass<V> result = new FinalBypass<V>(null);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.remove(key));
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public void putAll(Map<? extends K, ? extends V> m) {
		addContext(new QueueContext<Map<K,V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				item.putAll(m);
			}
		});
	}

	@Override
	public void clear() {
		addContext(new QueueContext<Map<K,V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				item.clear();
			}
		});
	}

	@Override
	public Set<K> keySet() {
		final FinalBypass<Set<K>> result = new FinalBypass<Set<K>>(null);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.keySet());
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public Collection<V> values() {
		final FinalBypass<Collection<V>> result = new FinalBypass<Collection<V>>(null);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.values());
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

	@Override
	public Set<Entry<K, V>> entrySet() {
		final FinalBypass<Set<Entry<K, V>>> result = new FinalBypass<Set<Entry<K,V>>>(null);
		final FinalBypass<Boolean> turn = FinalBypass.createBypass(Boolean.class);
		addContext(new QueueContext<Map<K, V>>() {

			@Override
			public void onContext(Map<K, V> item) {
				result.setObject(item.entrySet());
				turn.setObject(true);
			}
		});
		while (!turn.getObject());
		return result.getObject();
	}

}
